package com.md.util.system;

import com.md.base.exception.ServerException;
import com.md.value.enums.ErrorEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 刘骄阳
 * 2022-03-17 15:23
 */
@Slf4j
public final class Snowflake {
  //机器ID  2进制5位  32位减掉1位 31个
  private final long workerId;
  //机房ID 2进制5位  32位减掉1位 31个
  private final long datacenterId;
  //设置一个时间初始值    2^41 - 1   差不多可以用69年
  private final long startTime;
  //5位的机器id
  private final long workerIdBits = 5L;
  //5位的机房id
  private final long datacenterIdBits = 5L;
  /**
   * 序列号位数
   */
  private final long sequenceBits;
  //代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
  private long sequence;
  //记录产生时间毫秒数，判断是否是同1毫秒
  private long lastTimestamp = -1L;

  /**
   * 私有,只能通过内部创建
   *
   * @param workerId     工作Id
   * @param datacenterId 机器Id
   * @param sequence     序号
   */
  public Snowflake(final Long workerId, final Long datacenterId, final Long sequence, final Long startTime) {
    // 检查机房id和机器id是否超过31 不能小于0
    // -1L ^ (-1L << workerIdBits) 这个是二进制运算，就是5 bit最多只能有31个数字，也就是说机器id最多只能是32以内
    long maxWorkerId = ~(-1L << workerIdBits);
    if (workerId > maxWorkerId || workerId < 0) {
      throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    }

    // 这个是一个意思，就是5 bit最多只能有31个数字，机房id最多只能是32以内
    long maxDatacenterId = ~(-1L << datacenterIdBits);
    if (datacenterId > maxDatacenterId || datacenterId < 0) {

      throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
    this.sequence = sequence;
    this.startTime = startTime;
    this.sequenceBits = 12;
  }

  /**
   * 这个是核心方法，通过调用nextId()方法，让当前这台机器上的snowflake算法程序生成一个全局唯一的id
   *
   * @return 雪花id
   */
  public synchronized long nextId() {
    return nextId(0);
  }

  public synchronized long nextId(final Integer count) {
    if (count > 3) {
      log.error("clock is moving backwards. Rejecting requests until {}.", lastTimestamp);
      throw ServerException.build(ErrorEnum.SNOW_FLAKE_CREATE_ERROR);
    }
    try {
      // 这儿就是获取当前时间戳，单位是毫秒
      long timestamp = timeGen();
      if (timestamp < lastTimestamp) {
        log.error("Clock moved backwards. Refusing to generate id for {} milliseconds.", lastTimestamp - timestamp);
        return nextId(count + 1);
      }
      // 下面是说假设在同一个毫秒内，又发送了一个请求生成一个id
      // 这个时候就得把seqence序号给递增1，最多就是4096
      //每毫秒内产生的id数 2 的 12次方
      if (lastTimestamp == timestamp) {
        // 这个意思是说一个毫秒内最多只能有4096个数字，无论你传递多少进来，
        //这个位运算保证始终就是在4096这个范围内，避免你自己传递个sequence超过了4096这个范围
        long sequenceMask = ~(-1L << sequenceBits);
        sequence = (sequence + 1) & sequenceMask;
        //当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
        if (sequence == 0) {
          timestamp = tilNextMillis(lastTimestamp);
        }
      } else {
        sequence = 0;
      }
      // 这儿记录一下最近一次生成id的时间戳，单位是毫秒
      lastTimestamp = timestamp;
      // 这儿就是最核心的二进制位运算操作，生成一个64bit的id
      // 先将当前时间戳左移，放到41 bit那儿；将机房id左移放到5 bit那儿；将机器id左移放到5 bit那儿；将序号放最后12 bit
      // 最后拼接起来成一个64 bit的二进制数字，转换成10进制就是个long型
      long datacenterIdShift = sequenceBits + workerIdBits;
      long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
      return ((timestamp - startTime) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << sequenceBits) | sequence;
    } catch (Exception e) {
      return nextId(count + 1);
    }
  }

  /**
   * 当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
   *
   * @param lastTimestamp
   * @return
   */
  private long tilNextMillis(long lastTimestamp) {

    long timestamp = timeGen();

    while (timestamp <= lastTimestamp) {
      timestamp = timeGen();
    }
    return timestamp;
  }

  //获取当前时间戳
  private long timeGen() {
    return System.currentTimeMillis();
  }

}
